#include <iostream>
#include <sys/select.h>
#include "Sock.hpp"

int fdsArray[sizeof(fd_set) * 8] = {0};
int gnum = sizeof(fdsArray) / sizeof(fdsArray[0]);

#define DFL -1

using namespace std;

static void usage(std::string process)
{
    cerr << "\nUsage: " << process << " port\n"
         << endl;
}

static void showArray(int arr[], int num)
{
    cout << "当前合法sock list# ";
    for (int i = 0; i < num; i++)
    {
        if (arr[i] == DFL)
            continue;
        else
            cout << arr[i] << " ";
    }
    cout << endl;
}

// readfds里装的是已经就绪的文件描述符
void HandlerEvent(int listensock, fd_set &readfds)
{
    for (int i = 0; i < gnum; i++)//遍历寻找就绪的文件描述符
    {
        if (fdsArray[i] = DFL)
            continue;
        if (i == 0 && fdsArray[i] == listensock)
        {
            // 处理链接事件
            if (FD_ISSET(listensock, &readfds)) // 确定该fd是否就绪
            {
                // 具有一个新连接
                cout << "已经有一个新连接到来，需要进行（读取/拷贝了）" << endl;
                // accept接受客户端链接
                string clientip;
                uint16_t clientport = 0;
                int sock = Sock::Accept(listensock, &clientip, &clientport);
                if (sock < 0)
                    return;
                cout << "获取新连接成功: " << clientip << ":" << clientport << " | sock: " << sock << endl;

                // 将获取的新链接fd交给select托管
                int i = 0;
                for (; i < gnum; i++)      //寻找空位
                {
                    if (fdsArray[i] = DFL)
                        break;
                }

                if(i == gnum)
                {
                    cerr << "我的服务器已经到了最大的上限了，无法在承载更多同时保持的连接了" << endl;
                    close(sock);
                }
                else
                {
                    fdsArray[i] = sock;
                    showArray(fdsArray, gnum);
                }
            }
        }
        else
        {
            //处理普通的IO类事件
            if(FD_ISSET(fdsArray[i], &readfds))
            {
                //此处一定是一个合法就绪的文件描述符
                char buffer[1024];
                size_t s = recv(fdsArray[i], buffer, sizeof(buffer), 0);

                if(s > 0)
                {
                    buffer[s] = 0;
                    cout << "client[" << fdsArray[i] << "]# " << buffer << endl; 
                }
                else if (s == 0)//读缓冲区内没有数据可以读
                {
                    cout << "client[" << fdsArray[i] << "] quit, server close " << fdsArray[i] << endl;
                    close(fdsArray[i]);
                    fdsArray[i] = DFL; // 去除对该文件描述符的select事件监听
                    showArray(fdsArray, gnum);
                }
                else
                {
                    cout << "client[" << fdsArray[i] << "] error, server close " << fdsArray[i] << endl;
                    close(fdsArray[i]);
                    fdsArray[i] = DFL; // 去除对该文件描述符的select事件监听
                    showArray(fdsArray, gnum);
                }
                
                
            }
        }
    }
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        usage(argv[0]);
        exit(1);
    }

    int listensock = Sock::Socket();
    Sock::Bind(listensock, atoi(argv[1]));
    Sock::Listen(listensock);

    // 文件描述符存储数组初始化
    for (int i = 0; i < gnum; i++)
    {
        fdsArray[i] = DFL;
    }
    fdsArray[0] = listensock;
    while (true)
    {
        // 重设select参数
        int maxFd = DFL;
        fd_set readfds;
        FD_ZERO(&readfds);
        for (int i = 0; i < gnum; i++)
        {
            if (fdsArray[i] == DFL)
                continue;                  // 过滤不合法的fd
            FD_SET(fdsArray[i], &readfds); // 添加所有的合法的fd到readfds中，方便select统一进行就绪监听
            if (maxFd < fdsArray[i])
                maxFd = fdsArray[i];
        }
        struct timeval timeout = {100, 0};

        int n = select(maxFd + 1, &readfds, nullptr, nullptr, &timeout);
        switch (n)
        {
        case 0:
            cout << "time out ... : " << (unsigned long)time(nullptr) << endl;
            break;
        case -1:
            cerr << errno << " : " << strerror(errno) << endl;
            break;
        default:
            HandlerEvent(listensock, readfds);//有文件描述符就绪
            // 等待成功
            // 1. 刚启动的时候，只有一个fd，listensock
            // 2. server 运行的时候，sock才会慢慢变多
            // 3. select 使用位图，采用输出输出型参数的方式，来进行 内核<->用户 信息的传递， 每一次调用select，都需要对历史数据和sock进行重新设置！！！
            // 4. listensock，永远都要被设置进readfds中！
            // 5. select 就绪的时候，可能是listen 就绪，也可能是普通的IO sock就绪啦！！
            break;
        }
    }

    return 0;
}